#include"Renderer.h"
#include"Matrix.h"
#include<iostream>



Renderer::Renderer(HDC hdc, int scwidth, int scheight, Camera* cam)
{
	screenHdc = hdc;
	deviceWidth = scwidth;
	deviceHeight = scheight;
	camera = cam;
	texture = new Texture();
	texture->LoadTexture("grid.bmp");
}

Renderer::~Renderer()
{
}

void Renderer::Clear(DepthBuffer* zbuffer)
{
	BitBlt(screenHdc, 0, 0, deviceWidth, deviceHeight, NULL, NULL, NULL, BLACKNESS);
	for (int i = 0; i < zbuffer->height; i++) {
		for (int j = 0; j < zbuffer->width; j++) {
			zbuffer->depthBuffer[i][j] = 1;
		}
	}
}

//using vertex indices to draw
void Renderer::DrawByIndex(Mesh* m, Shader* shader, DepthBuffer* zbuffer)
{
	for (int i = 0; i < m->indexBuffer.size(); i=i + 4) {
		Vertex v1;
		v1.pos = m->positionBuffer[m->indexBuffer[i].x - 1];
		v1.uv = m->uvBuffer[m->indexBuffer[i].y - 1];
		v1.normal = m->normalBuffer[m->indexBuffer[i].z - 1];
		Vertex v2;
		v2.pos = m->positionBuffer[m->indexBuffer[i + 1].x - 1];
		v2.uv = m->uvBuffer[m->indexBuffer[i + 1].y - 1];
		v2.normal = m->normalBuffer[m->indexBuffer[i + 1].z - 1];
		Vertex v3;
		v3.pos = m->positionBuffer[m->indexBuffer[i + 2].x - 1];
		v3.uv = m->uvBuffer[m->indexBuffer[i + 2].y - 1];
		v3.normal = m->normalBuffer[m->indexBuffer[i + 2].z - 1];
		Vertex v4;
		v4.pos = m->positionBuffer[m->indexBuffer[i + 3].x - 1];
		v4.uv = m->uvBuffer[m->indexBuffer[i + 3].y - 1];
		v4.normal = m->normalBuffer[m->indexBuffer[i + 3].z - 1];

		DrawPrimitive(v1, v2, v3, shader, zbuffer);
		DrawPrimitive(v1, v3, v4, shader, zbuffer);
	}
}


//using vertex bufffer to draw
void Renderer::DrawByArray(Mesh* m, Shader* shader, DepthBuffer* zbuffer)
{
	for (int i = 0; i < m->vertexBuffer.size(); i=i + 3) {
		Vertex v1 = m->vertexBuffer[i];
		Vertex v2 = m->vertexBuffer[i + 1];
		Vertex v3 = m->vertexBuffer[i + 2];

		DrawPrimitive(v1, v2, v3, shader, zbuffer);
	}
}

void Renderer::DrawMesh(Mesh* m, Shader* shader, DepthBuffer* zbuffer)
{
	if (m->indexBuffer.size() > 0) {
		DrawByIndex(m, shader, zbuffer);
	}
	else {
		DrawByArray(m, shader, zbuffer);
	}
}

void Renderer::DrawPrimitive(Vertex v0, Vertex v1, Vertex v2, Shader* shader, DepthBuffer* zbuffer)
{
	shader->VS(v0, v1, v2);
	PreRasterization(v0, zbuffer);
	PreRasterization(v1, zbuffer);
	PreRasterization(v2, zbuffer);

	if (((int)v0.pos.y == (int)v1.pos.y && abs(v1.pos.y - v2.pos.y) <= 1) ||
		((int)v1.pos.y == (int)v2.pos.y && abs(v1.pos.y - v0.pos.y) <= 1) ||
		((int)v0.pos.y == (int)v2.pos.y && abs(v2.pos.y - v1.pos.y) <= 1)) {
		return;
	}
	RasterizeTriangle(v0, v1, v2, shader, zbuffer);
}


//divide a triangle into top-bottom twoparts, and use Bresham algorithm to draw each triangle
void Renderer::RasterizeTriangle(Vertex v0, Vertex v1, Vertex v2, Shader* shader, DepthBuffer* zbuffer)
{
	int iy0 = v0.pos.y;
	int iy1 = v1.pos.y;
	int iy2 = v2.pos.y;
	if (iy0 == iy1) {
		DrawTopTriangle(v0, v1, v2, shader, zbuffer, Vertex(), -1);
	}
	else if (iy1 == iy2) {
		DrawBottomTriangle(v0, v1, v2, shader, zbuffer, Vertex(), -1);
	}
	else {
		float xc = (v1.pos.y - v0.pos.y) * (v2.pos.x - v0.pos.x) / (v2.pos.y - v0.pos.y) + v0.pos.x;
		float yc = v1.pos.y;
		float t = (yc - v0.pos.y) / (v2.pos.y - v0.pos.y);

		Vertex vc(Vec4f(xc, yc, 0), Color(0, 0, 0, 0), Vec2(0, 0));
		vc.LerpVertex(v0, v2, t);
		DrawBottomTriangle(v0, v1, vc, shader, zbuffer, v2, 3);
		DrawTopTriangle(vc, v1, v2, shader, zbuffer, v0, 1);

	}
}

void Renderer::DrawTopTriangle(Vertex v0, Vertex v1, Vertex v2, Shader* shader, DepthBuffer* zbuffer, Vertex v3, int swapInd)
{
	float x0 = v0.pos.x, y0 = v0.pos.y;
	float x1 = v1.pos.x, y1 = v1.pos.y;
	float x2 = v2.pos.x, y2 = v2.pos.y;
	for (float y = y0; y <= y2; y++) {
		float t = (y - y0) / (y2 - y0);
		int lx = (y - y0) * (x2 - x0) / (y2 - y0) + x0;
		Vertex lv(Vec4f(lx, y, 0), Color(0, 0, 0, 0), Vec2(0, 0));
		lv.LerpVertex(v0, v2, t);

		int rx = (y - y1) * (x2 - x1) / (y2 - y1) + x1;
		Vertex rv(Vec4f(rx, y, 0), Color(0, 0, 0, 0), Vec2(0, 0));
		rv.LerpVertex(v1, v2, t);

		switch (swapInd)
		{
		case -1:
			DrawLine(lv, rv, shader, zbuffer, v0.pos, v1.pos, v2.pos);
			break;
		case 1:
			DrawLine(lv, rv, shader, zbuffer, v3.pos, v1.pos, v2.pos);
			break;
		case 2:
			DrawLine(lv, rv, shader, zbuffer, v0.pos, v3.pos, v2.pos);
			break;
		case 3:
			DrawLine(lv, rv, shader, zbuffer, v0.pos, v1.pos, v3.pos);
			break;
		default:
			DrawLine(lv, rv, shader, zbuffer, v0.pos, v1.pos, v2.pos);
			break;
		}
	}
}

void Renderer::DrawBottomTriangle(Vertex v0, Vertex v1, Vertex v2, Shader* shader, DepthBuffer* zbuffer, Vertex v3, int swapInd)
{
	float x0 = v0.pos.x, y0 = v0.pos.y;
	float x1 = v1.pos.x, y1 = v1.pos.y;
	float x2 = v2.pos.x, y2 = v2.pos.y;
	for (float y = y0; y <= y2; y++) {
		float t = (y - y0) / (y2 - y0);
		int lx = (y - y1) * (x0 - x1) / (y0 - y1) + x1;
		Vertex lv(Vec4f(lx, y, 0), Color(0, 0, 0, 0), Vec2(0, 0));
		lv.LerpVertex(v0, v1, t);

		int rx = (y - y2) * (x0 - x2) / (y0 - y2) + x2;
		Vertex rv(Vec4f(rx, y, 0), Color(0, 0, 0, 0), Vec2(0, 0));
		rv.LerpVertex(v0, v2, t);

		switch (swapInd)
		{
		case -1:
			DrawLine(lv, rv, shader, zbuffer, v0.pos, v1.pos, v2.pos);
			break;
		case 1:
			DrawLine(lv, rv, shader, zbuffer, v3.pos, v1.pos, v2.pos);
			break;
		case 2:
			DrawLine(lv, rv, shader, zbuffer, v0.pos, v3.pos, v2.pos);
			break;
		case 3:
			DrawLine(lv, rv, shader, zbuffer, v0.pos, v1.pos, v3.pos);
			break;
		default:
			DrawLine(lv, rv, shader, zbuffer, v0.pos, v1.pos, v2.pos);
			break;
		}
	}
}


//draw aline
void Renderer::DrawLine(Vertex v0, Vertex v1, Shader* shader, DepthBuffer* zbuffer, Vec4f p0, Vec4f p1, Vec4f p2)
{
	float x0 = v0.pos.x, y0 = v0.pos.y;
	float x1 = v1.pos.x, y1 = v1.pos.y;

	int dx = x1 - x0;
	int step = 1;
	if (dx < 0) {
		step = -1;
		dx = -dx;
	}
	int xc = x0, yc = y0;

	Vertex ver;
	if (x1 == x0) {
		if (ZTestAndWrite(xc, yc, (v0.pos.z + 1.f) / 2.0f, zbuffer)) {
			Vec4f g = centerOfGravity(p0, p1, p2, Vec2(xc, yc));
			if (shader->FS(v0, g)) {
				DrawPixel(xc, yc, v0.color);
			}
		}
		return;
	}



	for (int i = 0; i <= dx; i++) {
		//Perspective-Correct Interpolation
		float s = (xc - x0) / (x1 - x0); //screen space ratio;
		float t = s * v0.pos.z / (s * v0.pos.z + (1 - s) * v1.pos.z); //view space ratio
		float z = Vertex::LerpFloat(v0.pos.z, v1.pos.z, t);
		z = (1 + z) / 2.0;
		if (ZTestAndWrite(xc, yc, z, zbuffer)) {
			Color c = Color::Lerp(v0.color, v1.color, t);
			float u = Vertex::LerpFloat(v0.uv.x, v1.uv.x, t);
			float v = Vertex::LerpFloat(v0.uv.y, v1.uv.y, t);
			ver.pos = Vec4f(xc, yc, z);
			ver.color = c;
			ver.uv = Vec2(u, v);

			Vec4f g = centerOfGravity(p0, p1, p2, Vec2(xc, yc));
			if (shader->FS(ver, g)) {
				DrawPixel(xc, yc, ver.color);
			}

		}
		xc += step;

	}
}


//draw a pixel
void Renderer::DrawPixel(int x, int y,const Color& color)
{
	SetPixel(screenHdc, x, y, RGB(255 * color.r, 255 * color.g, 255 * color.b));
}


//ztest
bool Renderer::ZTestAndWrite(int x, int y, float depth, DepthBuffer* zbuffer)
{
	if (x >= 0 && x < zbuffer->width && y >= 0 && y < zbuffer->height) {
		if (zbuffer->depthBuffer[y][x] >= depth) {
			zbuffer->depthBuffer[y][x] = depth;
			return true;
		}
	}
	return false;
}

bool Renderer::CVVCheck(const Vertex& vertex)
{
	float wval = vertex.pos.w;
	if (vertex.pos.x<-wval || vertex.pos.x>wval) {
		return true;
	}
	if (vertex.pos.y<-wval || vertex.pos.y>wval) {
		return true;
	}
	if (vertex.pos.z<0.0f || vertex.pos.z>wval) {
		return true;
	}
	return false;

}

void Renderer::PreRasterization(Vertex& vertex, Buffer* buffer)
{
	float invw = 1.0f / vertex.pos.w;
	vertex.pos.x = (vertex.pos.x * invw + 1.0f) * 0.5f * (buffer->width - 1) + 0.5;
	vertex.pos.y = (vertex.pos.y * invw + 1.0f) * 0.5f * (buffer->height - 1) + 0.5;
}

void Renderer::DrawTriangle3D(const Vec4f& v1, const Vec4f& v2, const Vec4f& v3, const Matrix& mvp)
{
	//view space
	Vec4f vt1 = mvp.MultiplyVec4(v1);
	Vec4f vt2 = mvp.MultiplyVec4(v2);
	Vec4f vt3 = mvp.MultiplyVec4(v3);

	//screen space

	Vec4f vs1 = GetScreenCoord(vt1);
	Vec4f vs2 = GetScreenCoord(vt2);
	Vec4f vs3 = GetScreenCoord(vt3);

	DrawTriangle(vs1.x, vs1.y, vs2.x, vs2.y, vs3.x, vs3.y);
}

void Renderer::DrawTriangle(int x0, int y0, int x1, int y1, int x2, int y2)
{
	//let y0<y1<y2
	if (y1 < y0) {
		swap(x0, x1);
		swap(y0, y1);
	}
	if (y2 < y0) {
		swap(x0, x2);
		swap(y0, y2);

	}
	if (y2 < y1) {
		swap(x1, x2);
		swap(y1, y2);
	}

	if (y0 == y1) {
		DrawTopTriangle(x0, y0, x1, y1, x2, y2);
	}
	else if (y1 == y2) {
		DrawBottomTriangle(x0, y0, x1, y1, x2, y2);
	}
	else {
		int x3 = (y1 - y0) * (x2 - x0) / (y2 - y0) + x0;
		int y3 = y1;
		if (x1 > x3) {
			swap(x1, x3);
			swap(y1, y3);
			
		}
		DrawBottomTriangle(x0, y0, x1, y1, x3, y3);
		DrawTopTriangle(x1, y1, x3, y3, x2, y2);
	}
}

void Renderer::DrawTopTriangle(int x0, int y0, int x1, int y1, int x2, int y2)
{
	for (int y = y0; y <= y2; y++) {
		int lx = (y - y0) * (x2 - x0) / (y2 - y0) + x0;
		int rx = (y - y1) * (x2 - x1) / (y2 - y1) + x1;
		DrawLine(lx, y, rx, y);
	}

}

void Renderer::DrawBottomTriangle(int x0, int y0, int x1, int y1, int x2, int y2)
{
	for (int y = y0; y <= y1; y++) {
		int lx = (y - y1) * (x0 - x1) / (y0 - y1) + x1;
		int rx = (y - y2) * (x0 - x2) / (y0 - y2) + x2;
		DrawLine(lx, y, rx, y);
	}
}


//draw a line using Bresham
void Renderer::DrawLine(int x0, int y0, int x1, int y1)
{
	int dx = x1 - x0;
	int dy = y1 - y0;

	int stepx = 1, stepy = 1;

	if (dx < 0) {
		stepx = -1;
		dx = -dx;
	}
	if (dy < 0) {
		stepy = -1;
		dy = -dy;
	}

	int dx2 = dx << 1, dy2 = dy << 1;

	int  xc = x0, yc = y0, err;
	if (dy < dx) {
		err = dy2 - dx;
		for (int i = 0; i <= dx; i++) {
			DrawPixel(xc, yc);
			xc += stepx;
			err += dy2;
			if (err >= 0) {
				err -= dx2;
				yc += stepy;
			}
		}
	}
	else {
		err = dx2 - dy;
		for (int i = 0; i <= dy; i++) {
			DrawPixel(xc, yc);
			yc += stepy;
			err += dx2;
			if (err >= 0) {
				err -= dy2;
				xc += stepx;
			}
		}
	}

}

void Renderer::DrawPixel(int x, int y)
{
	SetPixel(screenHdc, x, y, RGB(255, 255, 0));
}

//get coord int the screen space
Vec4f Renderer::GetScreenCoord(const Vec4f& vec)
{
	float invw = 1.0f / vec.w;
	float x = (vec.x * invw + 1.0f) * 0.5f * deviceWidth;
	float y = (1.0f - vec.y * invw) * 0.5f * deviceHeight;
	float z = 1.0f / vec.z;

	return Vec4f(x, y, z);
}








